﻿using System;
using System.Collections.Generic;
using Cosmos.IL2CPU.X86.IL;
using DimOS.Constants;
using DimOS.Internals;
using DimOS.Plugs;
using String = DimOS.Plugs.String;
using Array = DimOS.Plugs.Array;
using SH = DimOS.DimSH;

namespace DimOS.H
{
    public class Script
    {
        public int Pointer { get; set; }

        public string Data { get; set; }

        public string Name { get; set; }

        public Script(string data, int pointer = 0)
        {
            Data = data;
            Pointer = pointer;
        }

        public Script(Script source)
        {
            Name = source.Name;
            Data = source.Data;
            Pointer = source.Pointer;         
        }

        public bool Valid { get { return Pointer < Data.Length; } }

        public string Substring(int start = -2, int length = -1)
        {
            start = start == -2 ? (Pointer < Data.Length - 1 ?
            Pointer : Data.Length - 1) : start == -1 ? 0 : start;

            return length < 0 || length >= Data.Length - start ?
            Data.Substring(start) : Data.Substring(start, length);
        }

        public void Traverse(int step) { Pointer += step
            < 0 ? (Pointer >= -step ? step : 0) : step; }

        public int Proceed(bool once = false)
        {
            var stop = 0; while (Valid) { switch (Data[Pointer]) {
            case Tokens.End: if (once) goto case Tokens.CommandRight;
            goto case Tokens.ParameterRight; case Tokens.CommandRight:
            stop++; Traverse(1); return stop; case Tokens.CommandLeft:
            /*case Tokens.VerbatimString:*/ case Tokens.Space: case Tokens
            .ParameterRight: Traverse(1); break; default: return stop;
            } } return stop;
        }

        public Value Execute(int start, char stop = '\0', bool execute = true)
        { Pointer = start; return Execute(stop == '\0'
          ? Tokens.ListStop : new []{stop}, execute); }

        public Value Execute(char stop = '\0', bool execute = true)
        { return Execute(Pointer, stop, execute); }

        public bool Check(char stop = '\0')
        { try { Execute(Pointer, stop, false);
          return true; } catch { return false; } }

        public Value Execute(char[] stop, bool execute = true, int end = -1)
        { if (!String.EndsWith(Data, Tokens.End.ToString())) Data +=
          Tokens.End; return Parser.Process(this, stop, execute, end); }

        public Value ExecuteAll(int start = 0)
        { var result = Value.Empty; Pointer = start; while (Valid && (result == null
          || !result.Return)) { result = Execute(); Proceed(); } return result; }

        public bool CheckAll()
        { var result = true; while (Valid && result) {
          result = Check(); Proceed(); } return result; }

        public Value GetValue(int end = -1)
        {
            if (Valid && (Data[Pointer] == Tokens.Separator
               || Data[Pointer] == Tokens.Space)) Traverse(1);

            if (!Valid) throw new InvalidOperationException
            ("Incomplete function definition encountered.");

            var enclosed = Data[Pointer] == Tokens.VerbatimString;

            if (Data[Pointer] == Tokens.ValueLeft) { Traverse(1);
            bool list; return new Value { List = GetParameters
            (Tokens.ValueLeft, Tokens.ValueRight, out list) };
            } var temp = Execute(Tokens.ListProcess, true, end)
            .Clone(); if (enclosed && Valid && Data[Pointer] ==
            Tokens.VerbatimString) Traverse(1); if (Valid && Data
            [Pointer] == Tokens.Space) Traverse(1); return temp;
        }

        public List<Value> GetParameters(char start, char end, out bool list)
        { var values = new List<Value>(); list = Valid && Data[Pointer - 1] ==
          Tokens.ValueLeft; if (!Valid || Data[Pointer] == Tokens.End) return
          values; var temp = new Script(Data, Pointer); temp.GetChunk(start,
          end); while (Pointer < temp.Pointer) { var value = GetValue(list ?
          temp.Pointer : -1); values.Add(value); } if (Pointer <= temp.Pointer
          && Valid && (Data[Pointer] == Tokens.ParameterRight || Data[Pointer]
          == Tokens.ValueRight)) Traverse(1); if (Valid && Data[Pointer] ==
          Tokens.Space) Traverse(1); return values; }

        public string GetChunk(char start, char end)
        {
            var chunk = ""; var enclosure = 0;
            for (; Valid; Traverse(1)) { var temp = Data[Pointer];
                if (string.IsNullOrWhiteSpace(temp.ToString()) &&
                chunk.Length == 0) continue; if (temp == start)
                enclosure++; else if (temp == end) enclosure--;
                chunk += temp; if (enclosure == -1) { if (temp == end)
                chunk = chunk.Remove(chunk.Length - 1, 1); break; }
            } return chunk;
        }

        public string GetConstruct(char[] stop)
        {
            var current = Pointer < Data.Length ? Data[Pointer] : Tokens.Empty;
            var previous = Pointer >= 1 ? Data[Pointer - 1] : Tokens.Empty;
            var result = "";

            if (!Array.Contains(stop, Tokens.Space)) while (current == Tokens
            .Space && previous != Tokens.VerbatimString) { Traverse(1); current = Pointer
            < Data.Length ? Data[Pointer] : Tokens.Empty; previous = Pointer >= 1
            ? Data[Pointer - 1] : Tokens.Empty; } if (current == Tokens.VerbatimString)
            {
                var enclosure = Data.IndexOf(Tokens.VerbatimString, Pointer + 1);
                if (enclosure == -1) throw new InvalidOperationException("Unmatched"
                + " string specifiers encountered [" + Substring(Pointer - 1, Engine
                .OutputLimit) + "]."); result = Substring(Pointer + 1, enclosure -
                Pointer - 1); Pointer = enclosure + 1; return result;
            }

            if (Valid && Data[Pointer] == Tokens.VerbatimString) Traverse(1); var end = 
            String.IndexOfAny(Data, stop, Pointer); end = end < 0 ? Data.Length : end;

            while (end > 0 && end + 1 < Data.Length && Data[end - 1] == '\\') end
            = String.IndexOfAny(Data, stop, end + 1); end = end < 0 ? Data.Length
            : end; if (Data[end - 1] == Tokens.VerbatimString) end--; result = String
            .Replace(Substring(Pointer, end - Pointer), "\\\"", "\""); Pointer = end;

            if (Valid && (Data[Pointer] == Tokens.VerbatimString
            || Data[Pointer] == Tokens.Space)) Traverse(1);

            return result;
        }

        public string GetConstruct()
        {
            if (!Valid) return ""; var end = String.IndexOfAny(Data,
            Tokens.Separators, Pointer); if (end < 0) return ""; var
            result = Substring(Pointer, end - Pointer); Pointer = end;
            return result;
        }

        public static string Normalize(string source)
        { var stub = new List<Error>(); return Normalize(source, ref stub); }

        public static string Normalize(string source, ref List<Error> errors)
        {
            var spaceAllowed = false; var verbatimString = false; var prologueComment =
            false; var inlineComment = false; var lastCharacter = Tokens.Empty; var result
            = ""; var forLoop = false; int commandLeft = 0, commandRight = 0, parameterLeft
            = 0, parameterRight = 0; var shell = false; int length = 0, counter = 0, commands
            = 0, parameters = 0; for (int i = 0; i < source.Length; i++) { var character =
            source[i]; var nextCharacter = i + 1 < source.Length ? source[i + 1] : Tokens
            .Empty; if (character == Tokens.NewLine && result.Length > length) length =
            result.Length; if (prologueComment && ((inlineComment && character != Tokens
            .NewLine) || (!inlineComment && character != Tokens.WildcardAny))) continue;

            switch (character)
            {
                case Tokens.MirrorLeft:
                    if (!verbatimString && (prologueComment || nextCharacter ==
                    Tokens.MirrorLeft || nextCharacter == Tokens.WildcardAny))
                    { prologueComment = true; inlineComment = inlineComment ||
                    nextCharacter == Tokens.MirrorLeft; continue; } break;

                case Tokens.MirrorRight:
                    if (!verbatimString && (prologueComment || nextCharacter ==
                    Tokens.MirrorRight || nextCharacter == Tokens.WildcardAny))
                    { prologueComment = true; inlineComment = inlineComment ||
                    nextCharacter == Tokens.MirrorRight; continue; } break;

                case Tokens.WildcardAny:
                    if (prologueComment && (nextCharacter == Tokens.MirrorLeft
                    || nextCharacter == Tokens.MirrorRight)) { i++; prologueComment
                    = false; continue; } break;

                case Tokens.VerbatimString:
                    if (!prologueComment && ((!verbatimString && !shell) ||
                    (lastCharacter != Tokens.MirrorLeft && lastCharacter !=
                    Tokens.MirrorRight))) verbatimString = !verbatimString;
                    break;

                case Tokens.Space:
                    if (verbatimString) result += character; else { for (int j = 0; j <
                    Commands.List[0].Count; j++) { var key = Commands.List[0][j].Key; if
                    (String.EndsWith(result, key) && !String.EndsWith(result, Tokens.Dot +
                    key)) { shell = true; counter = 0; spaceAllowed = true; break; } } var
                    space = false; if (!(nextCharacter == Tokens.Space || nextCharacter ==
                    Tokens.CommandLeft || nextCharacter == Tokens.ParameterLeft || nextCharacter
                    == Tokens.ValueLeft || nextCharacter == Tokens.Empty)) for (int k = 0; k <
                    Tokens.SpaceAllowed.Count; k++) { if (String.EndsWith(result.ToLower(), Tokens
                    .SpaceAllowed[k].ToLower())) if (Array.Contains(Tokens.Separators, (Tokens
                    .SpaceAllowed[k].Length >= result.Length ? Tokens.End : result[result.Length -
                    Tokens.SpaceAllowed[k].Length -1]))) { space = true; break; } } spaceAllowed =
                    space || (lastCharacter != Tokens.Empty && lastCharacter != Tokens.Separator &&
                    spaceAllowed); space = false; for (int l = 0; l < Tokens.SpaceAllowedOnce.Count;
                    l++) { if (String.EndsWith(result.ToLower(), Tokens.SpaceAllowedOnce[l].ToLower
                    ())) if (Array.Contains(Tokens.Separators, (Tokens.SpaceAllowedOnce[l].Length
                    >= result.Length ? Tokens.End : result[result.Length - Tokens.SpaceAllowedOnce
                    [l].Length - 1]))) { space = true; break; } } if ((spaceAllowed || space) &&
                    lastCharacter != Tokens.Space && nextCharacter != Tokens.CommandRight &&
                    nextCharacter != Tokens.End && nextCharacter != Tokens.Space) { result +=
                    character; lastCharacter = character; } } continue;

                case '\t':
                case '\r':
                    if (verbatimString) result += character; continue;

                case Tokens.NewLine:
                    if (inlineComment) prologueComment = inlineComment = false;
                    if (!verbatimString) { spaceAllowed = false; continue; } break;

                case Tokens.ParameterLeft:
                    if (!verbatimString) { if (forLoop)
                    parameterLeft++; parameters++; } break;

                case Tokens.ParameterRight:
                    if (!verbatimString) { spaceAllowed = false; if (forLoop) 
                    { parameterRight++; if (parameterLeft == parameterRight)
                    { parameterLeft = parameterRight = 0; forLoop = false; }
                    } parameters--; } break;

                case Tokens.CommandLeft:
                    if (!verbatimString) { if (forLoop || shell) commandLeft++; if
                    (shell) { spaceAllowed = false; if (counter < 1) counter++; }
                    commands++; } break;

                case Tokens.CommandRight:
                    if (!verbatimString) { if (forLoop || shell) commandRight++; if
                    (shell) { counter--; if (commandLeft < commandRight) {  shell
                    = false; commandLeft = commandRight = 0; spaceAllowed = false; 
                    } else if (((commandLeft % 2 == 0) == (commandRight % 2 == 0))
                    || counter < 0) spaceAllowed = true; break; } spaceAllowed =
                    false; commands--; } break;

                case Tokens.End:
                    if (!verbatimString && !forLoop) { if (shell && commandLeft ==
                    commandRight) { shell = false; commandLeft = commandRight = 0; 
                    } else if ((lastCharacter == Tokens.CommandRight && (!shell ||
                    !spaceAllowed)) || lastCharacter == Tokens.End) { spaceAllowed
                    = false; continue; } spaceAllowed = false; } break;

                case Tokens.DefineShort:
                    if (!verbatimString && !prologueComment) { if (lastCharacter ==
                    Tokens.DefineShort || lastCharacter == Tokens.Not || nextCharacter
                    != Tokens.DefineShort) { result += character.ToString() + Tokens
                    .Space; lastCharacter = character; spaceAllowed = false; continue; } }
                    break;

                case Tokens.SizeShort:
                    if (!verbatimString && !prologueComment) { if (lastCharacter !=
                    Tokens.SizeShort && nextCharacter != Tokens.Not && nextCharacter
                    != Tokens.SizeShort && nextCharacter != Tokens.ParameterLeft &&
                    nextCharacter != Tokens.Space) { result += character.ToString()
                    + Tokens.Space; lastCharacter = character; spaceAllowed = false;
                    continue; } } break;

                case Tokens.TerminalShort:
                    if (!verbatimString && !prologueComment) { if (lastCharacter !=
                    Tokens.TerminalShort && nextCharacter != Tokens.TerminalShort
                    &&  nextCharacter != Tokens.ParameterLeft && nextCharacter !=
                    Tokens.Space) { result += character.ToString() + Tokens.Space;
                    lastCharacter = character; spaceAllowed = false; continue; } }
                    break;

                case Tokens.LoopShort:
                    if (!verbatimString && !prologueComment && lastCharacter !=
                    Tokens.LoopShort && lastCharacter != Tokens.Not && nextCharacter
                    != Tokens.LoopShort) forLoop = true; break;

                case Tokens.Dot:
                    for (int j = 0; j < Commands.List[0].Count; j++) { var key = Commands.List[0][j].Key;
                    if (String.EndsWith(result, key) && !String.EndsWith(result, Tokens.Dot + key)) { shell
                    = true; counter = 0;  spaceAllowed = true; break; } for (int k = 0; k < Commands.List[0]
                    [j].Aliases.Length; k++) { var alias = Commands.List[0][j].Aliases[k]; if (String.EndsWith
                    (result, alias) && !String.EndsWith(result, Tokens.Dot + alias)) { shell = true; counter
                    = 0; spaceAllowed = true; break; } } } break;
            }
                
            if (!verbatimString && !prologueComment && String.EndsWith(result, Tokens.Loop)) forLoop =
            true; if (!prologueComment) { result += character; if (!verbatimString) for (int m = 0; m <
            DimSH.Aliases.List.Count; m++) if (String.EndsWith(result, SH.Aliases.List[m].Name) && Array
            .Contains(Tokens.Separators, result[result.Length - SH.Aliases.List[m].Name.Length - 1]) &&
            (nextCharacter == Tokens.Empty || Array.Contains(Tokens.Separators, nextCharacter)) && !String
            .EndsWith(result, Tokens.Dot + SH.Aliases.List[m].Name)) { result = result.Substring(0, result
            .Length - SH.Aliases.List[m].Name.Length) + Normalize(SH.Aliases.List[m].Value, ref errors);
            shell = true; counter = 0; spaceAllowed = true; break; } if (character == Tokens.ParameterLeft &&
            nextCharacter == Tokens.ParameterRight && (lastCharacter == Tokens.IfShort[0] || lastCharacter ==
            Tokens.DefineShort || lastCharacter == Tokens.LoopShort) && !shell) errors.Add(Error.Mismatch);
            if (character == Tokens.CommandLeft && nextCharacter == Tokens.CommandRight && lastCharacter ==
            Tokens.ParameterRight && !shell) errors.Add(Error.Other); if (Array.Contains(Tokens.ListProcess,
            character) && (lastCharacter == Tokens.ParameterLeft && nextCharacter == Tokens.ParameterRight)
            || (lastCharacter == Tokens.ValueLeft && nextCharacter == Tokens.ValueRight) || (lastCharacter
            == Tokens.CommandLeft && nextCharacter == Tokens.CommandRight) || (lastCharacter == Tokens.Not
            && character != Tokens.Space) && !shell) errors.Add(Error.InvalidArgument); } lastCharacter =
            character; } if (commands != 0) errors.Add(Error.WrongLevel); if (verbatimString) errors.Add
            (Error.InvalidName); if (parameters != 0) errors.Add(Error.UnmatchedSpecifier); return result;
        }
    }
}
